package com.jgp.attachment.service.impl;

import com.alibaba.simpleimage.ImageWrapper;
import com.alibaba.simpleimage.util.ImageReadHelper;
import com.jgp.attachment.exception.DocErrorCode;
import com.jgp.attachment.exception.DocException;
import com.jgp.attachment.model.DocType;
import com.jgp.attachment.model.FileInfo;
import com.jgp.attachment.model.MountObject;
import com.jgp.attachment.props.AttachmentConfigPropBean;
import com.jgp.attachment.repository.FileInfoRepository;
import com.jgp.attachment.repository.MountObjectRepository;
import com.jgp.attachment.service.DocService;
import com.jgp.attachment.utils.MimeUtil;
import com.jgp.common.utils.FileUtil;
import com.jgp.common.utils.JGPUtil;
import com.jgp.media.image.ImageUtil;
import com.jgp.sys.query.Operator;
import com.jgp.sys.query.QueryFilterList;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 项目   parent
 * 作者   loufei
 * 时间   2018/4/28
 */
@Service
public class DocServiceImpl implements DocService {
    
    Logger logger = LoggerFactory.getLogger(DocServiceImpl.class);
    
    @Autowired
    private AttachmentConfigPropBean attachmentConfigPropBean;
    
    @Autowired
    private FileInfoRepository fileInfoRepository;
    
    @Autowired
    private MountObjectRepository mountObjectRepository;
    
    /**
     * 上传文件
     *
     * @param file    MultipartFile 文件
     * @param request HttpServletRequest
     * @return 若有挂载对象则返回null，没有挂载对象返回 文件id
     */
    @Transactional
    @Override
    public FileInfo upload(MultipartFile file, HttpServletRequest request) throws DocException {
        String localDir = attachmentConfigPropBean.getLocalDir();
        String diskName = request.getParameter("uuid");
        String md5 = null;
        if (attachmentConfigPropBean.getMd5Check()) {
            md5 = request.getParameter("md5");
        }
        //分片信息
        String chunksStr = request.getParameter("chunks");
        String chunkStr = request.getParameter("chunk");
        
        //上传物理路径
        String uploadPath;
        
        if (Objects.nonNull(localDir) && new File(localDir).exists()) {
            uploadPath = localDir;
        } else {
            uploadPath = request.getServletContext().getRealPath("/upload");
        }
        
        if (!Files.exists(Paths.get(attachmentConfigPropBean.getLocalDir())) || !Files
                .exists(Paths.get(attachmentConfigPropBean.getLocalTempDir()))) {
            throw new DocException(DocErrorCode.DIR_NOT_FOUND);
        }
        //本地服务器存储
        if (attachmentConfigPropBean.getLocalEnable()) {
            try {
                if (file != null) {
                    String originalFilename = file.getOriginalFilename();
                    String ext = originalFilename.substring(originalFilename.lastIndexOf(".")+1);
                    
                    File diskFile = new File(uploadPath, diskName + "." + ext);
                    File tempFileDir = new File(uploadPath, "temp");
                    File tempChunkDir = new File(tempFileDir, diskName);
                    if (!tempChunkDir.exists()) {
                        tempChunkDir.mkdirs();
                    }
                    //分片处理
                    if (Objects.nonNull(chunksStr)) {
                        Integer chunks = Integer.parseInt(chunksStr);
                        Integer chunk = Integer.parseInt(chunkStr);
                        String chuckMd5 = null;
                        if (Objects.nonNull(md5)) {
                            chuckMd5 = request.getParameter("chuck_md5");
                        }
                        
                        File part = new File(tempChunkDir, (chuckMd5==null?diskName:chuckMd5) + "." + chunk + "_part");
                        FileUtils.copyInputStreamToFile(file.getInputStream(), part);
                        
                        File[] files = tempChunkDir.listFiles((dir, name) -> name.endsWith("_part"));
                        
                        if (files.length == chunks) {
                            
                            if (!diskFile.exists()) {
                                diskFile.createNewFile();
                            }
                            List<File> list = Arrays.asList(files).parallelStream().sorted((o1, o2) -> {
                                String suffix1 = o1.getName().split("\\.")[1];
                                //Integer index1 = Integer.parseInt(suffix1.split("_")[0]);
                                String suffix2 = o2.getName().split("\\.")[1];
                                //Integer index2 = Integer.parseInt(suffix2.split("_")[0]);
                                return suffix1.compareTo(suffix2);
                            }).collect(Collectors.toList());
                            FileUtil.mergeAndRemoveTempFile(list, diskFile);
                        }
                    } else {
                        FileUtils.copyInputStreamToFile(file.getInputStream(), diskFile);
                    }
                    
                    if (diskFile.exists()) {
                        FileInfo fileInfo = this.initFileInfo(file, diskFile, md5);
                        FileInfo persistFileInfo = fileInfoRepository.create(fileInfo);
                        String object_params = request.getParameter("object_params");
                        String water = request.getParameter("water");
                        if(StringUtils.isNotBlank(object_params)){
                            List<Map> objects = JGPUtil.jsonToList(object_params,Map.class);
        
                            for (Map<String, String> object : objects) {
                                String objectId = object.get("object_id");
                                String objectType = object.get("object_type");
                                if (StringUtils.isNotBlank(objectId) && StringUtils.isNotBlank(objectType)) {
                                    mountFilesToObject(objectId, objectType, persistFileInfo.getId());
                                }
                            }
        
                        }else {
                            String objectId = request.getParameter("object_id");
                            String objectType = request.getParameter("object_type");
        
                            //有挂载对象则在挂载关系表中创建关联关系
                            //没有挂载对象则返回上传后的文件id
                            if (StringUtils.isNotBlank(objectId) && StringUtils.isNotBlank(objectType)) {
                                mountFilesToObject(objectId, objectType, persistFileInfo.getId());
                            }
                        }
                        String extName = fileInfo.getExt().toLowerCase();
                        if(extName.equals("jpeg")||extName.equals("jpg")||extName.equals("gif")||extName.equals("png")||extName.equals("bmp")||extName.equals("tiff")){
                            ImageWrapper imageWrapper = ImageReadHelper.read(file.getInputStream());
                            generateWater(persistFileInfo,imageWrapper.getWidth(),imageWrapper.getHeight(),water);
                        }
                        generateThumbs(persistFileInfo,water);
                        return persistFileInfo;
                        
                    }
                    
                }
            } catch (Exception e) {
                e.printStackTrace();
                //上传异常
                throw new DocException(DocErrorCode.UPLOAD_FAILED);
            }
        } else {
            //未开启本地存储
            throw new DocException(DocErrorCode.LOCAL_STORAGE_OFF);
        }
        return null;
    }
    @Transactional
    @Override
    public FileInfo uploadEditorImage(MultipartFile file, HttpServletRequest request) throws DocException {
        
        String localDir = attachmentConfigPropBean.getLocalDir();
        String diskName = System.currentTimeMillis()+"_"+JGPUtil.random(6);
    
        //上传物理路径
        String uploadPath;
    
        if (Objects.nonNull(localDir) && new File(localDir).exists()) {
            uploadPath = localDir;
        } else {
            uploadPath = request.getServletContext().getRealPath("/upload");
        }
    
        uploadPath+= File.separator+"workflow"+File.separator+"image";
    
        if (!Files.exists(Paths.get(attachmentConfigPropBean.getLocalDir())) || !Files
                .exists(Paths.get(attachmentConfigPropBean.getLocalTempDir()))) {
            throw new DocException(DocErrorCode.DIR_NOT_FOUND);
        }
    
        //本地服务器存储
        if (attachmentConfigPropBean.getLocalEnable()) {
            try {
                if (file != null) {
                    String ext = file.getOriginalFilename().split("\\.")[1];
                
                    File diskFile = new File(uploadPath, diskName + "." + ext);
                    //分片处理
                    FileUtils.copyInputStreamToFile(file.getInputStream(), diskFile);
                
                    if (diskFile.exists()) {
                    
                        FileInfo fileInfo = this.initFileInfo(file, diskFile, null);
                        fileInfo.setPath("/workflow/image/"+diskName+ "." + ext);
                        FileInfo persistFileInfo = fileInfoRepository.create(fileInfo);
                        String objectId = request.getParameter("object_id");
                        String objectType = "EDITOR";
                    
                        //有挂载对象则在挂载关系表中创建关联关系
                        //没有挂载对象则返回上传后的文件id
                        if (StringUtils.isNotBlank(objectId) && StringUtils.isNotBlank(objectType)) {
                            mountFilesToObject(objectId, objectType, persistFileInfo.getId());
                        }
                    
                        return persistFileInfo;
                    
                    }
                
                }
            } catch (Exception e) {
                //上传异常
                throw new DocException(DocErrorCode.UPLOAD_FAILED);
            }
        } else {
            //未开启本地存储
            throw new DocException(DocErrorCode.LOCAL_STORAGE_OFF);
        }
        return null;
    }
    
    private void generateThumbs(FileInfo fileInfo,String water) {
        if(fileInfo.getExt().toLowerCase().equals("pdf")) return ;
        if(fileInfo.getDocType().equals(DocType.IMAGE)){
            List<String> thumbSizes = attachmentConfigPropBean.getThumb().get("image");
            for (String thumbSize : thumbSizes) {
                String [] sizes = thumbSize.split(",");
                ImageUtil.start(()->{
                    ImageWrapper imageWrapper;
                    int w = Integer.parseInt(sizes[0]);
                    int h = Integer.parseInt(sizes[1]);
                    if(StringUtils.isNotBlank(water)&&w>300){
                        imageWrapper = ImageUtil.scaleWithWaterText(fileInfo.getDiskPath(),water, w, h);
                    }else{
                        imageWrapper = ImageUtil.scale(fileInfo.getDiskPath(), w, h);
                    }
                    
                    String target = fileInfo.getDiskDir()+File.separator+"thumb_"+thumbSize+"_"+fileInfo.getDiskName();
                    ImageUtil.writeImage(imageWrapper,target,"jpg");
                });
                
            }
        }
    }
    
    private void generateWater(FileInfo fileInfo,int w,int h,String water) {
        ImageWrapper imageWrapper = ImageUtil.scaleWithWaterText(fileInfo.getDiskPath(),water, w, h);
        String target = fileInfo.getDiskDir()+File.separator+"water_"+fileInfo.getDiskName();
        ImageUtil.writeImage(imageWrapper,target,"jpg");
    }
    
    /**
     * md5检测
     *
     * @param md5 前端组件计算的md5值
     * @return 文件存在则返回 FileInfo对象，否则返回null
     */
    @Override
    public FileInfo queryByMd5(String md5) {
        QueryFilterList filters = new QueryFilterList();
        filters.addFilter("md5", Operator.eq, md5);
        List<FileInfo> files = fileInfoRepository.read(filters);
        if (files.size() > 0) {
            return files.get(0);
        }
        return null;
    }
    
    /**
     * 查询指定对象和文档的所有关联关系
     *
     * @param objectId   挂载对象id
     * @param objectType 挂载对象类型
     * @return 返回关联关系集合
     */
    @Override
    public List<MountObject> queryMountFiles(String objectId, String objectType) {
        List<MountObject> mountObjects = null;
        QueryFilterList filters = new QueryFilterList();
        filters.addFilter("objectId", "eq", objectId);
        if (StringUtils.isNotBlank(objectType)) {
            filters.addFilter("objectType", "eq", objectType);
        }
        return mountObjectRepository.read(filters);
    }
    
    @Override
    public long updateMountFiles(List<MountObject> mountObjects) {
        return mountObjectRepository.updateInBatch(mountObjects);
    }
    
    @Override
    public List<MountObject> createMountFiles(List<MountObject> mountObjects) {
        return mountObjectRepository.createInBatch(mountObjects);
    }
    
    @Override
    public List<MountObject> queryMountFiles(String fileId) {
        List<MountObject> mountObjects = null;
        if (StringUtils.isNotBlank(fileId)) {
            QueryFilterList filters = new QueryFilterList();
            filters.addFilter("fileId", "eq", fileId);
            mountObjects = mountObjectRepository.read(filters);
        }
        return mountObjects;
    }
    
    
    @Override
    public long countMountFiles(String fileId) {
        if (StringUtils.isNotBlank(fileId)) {
            QueryFilterList filters = new QueryFilterList();
            filters.addFilter("fileId", "eq", fileId);
            return mountObjectRepository.count(filters);
        }
        return 0;
    }
    
    /**
     * 根据一个文件id查询文件对象
     * @param fileId 文件id
     * @return {@link FileInfo}
     */
    @Override
    public FileInfo queryFile(String fileId) {
        return fileInfoRepository.read(fileId);
    }
    
    /**
     *
     * 根据一个文件id查询物理文件对象
     * @param fileId 文件id
     * @return 物理文件
     */
    @Override
    public File queryIOFile(String fileId) {
        return fileInfoToIOFile(queryFile(fileId));
    }
    
    /**
     * 根据文件id查询文件对象
     *
     * @param fileIds 文件id集合
     * @return {@link FileInfo} 集合
     */
    @Override
    public List<FileInfo> queryFiles(List<String> fileIds) {
        return fileInfoRepository.readAll(fileIds);
    }
    
    
    /**
     * 查询挂载对象下的所有文件
     *
     * @param objectId   挂载对象id
     * @param objectType 挂载对象类型
     * @return 文件集合
     */
    @Override
    public List<FileInfo> queryFiles(String objectId, String objectType) {
        List<FileInfo> files = null;
        if (StringUtils.isNotBlank(objectId) && StringUtils.isNotBlank(objectType)) {
            List<MountObject> mountObjects = queryMountFiles(objectId, objectType);
            if (Objects.nonNull(mountObjects)) {
                files = fileInfoRepository.readAll(
                        mountObjects.stream().map(MountObject::getFileId).collect(Collectors.toList()));
            }
        }
        if(Objects.nonNull(files)) {
            return files.stream().sorted((o1, o2) -> {
                Long o1ct = o1.getCreateTimestamp();
                Long o2ct = o2.getCreateTimestamp();
                return o1ct>o2ct?1:(o1ct==o2ct?0:-1);
            }).collect(Collectors.toList());
        }
        return files;
    }
    
    
    /**
     * 更新或创建挂载关系
     *
     * @param objectId   挂载对象id
     * @param objectType 挂载对象类型
     * @param fileIds    文件id
     */
    @Transactional
    @Override
    public void mountFilesToObject(String objectId, String objectType, String... fileIds) {
        List<MountObject> mountObjects = queryMountFiles(objectId, objectType);
        
        Map<String, MountObject> existFileMap = new HashMap<>();
        mountObjects.forEach(mo -> existFileMap.put(mo.getFileId(), mo));
        
        List<MountObject> createMos = new ArrayList<>();
        
        for (String fileId : fileIds) {
            if (!existFileMap.containsKey(fileId)) {
                MountObject mountObject = new MountObject();
                mountObject.setFileId(fileId);
                mountObject.setObjectId(objectId);
                mountObject.setObjectType(objectType);
                createMos.add(mountObject);
            }
        }
        
        if (createMos.size() > 0) {
            mountObjectRepository.createInBatch(createMos);
        }
    }
    
    /**
     * 获取io file 对象
     *
     * @param fileIds 文件id集合
     * @return 只返回存在的文件
     */
    @Override
    public List<File> queryIOFiles(List<String> fileIds) {
        return fileInfoToIOFile(queryFiles(fileIds));
    }
    
    /**
     * 获取io file 对象
     *
     * @param objectId   挂载对象
     * @param objectType 挂载对象类型
     * @return io file 集合
     */
    @Override
    public List<File> queryIOFiles(String objectId, String objectType) {
        return fileInfoToIOFile(queryFiles(objectId, objectType));
    }
    
    @Transactional
    @Override
    public void removeFile(String objectId, String objectType, String fileId) {
        boolean removeDiskFileFlag = false;
        if(countMountFiles(fileId)==1){
            removeDiskFileFlag = true;
        }
    
        QueryFilterList filters = new QueryFilterList();
        filters.addFilter("fileId", "eq", fileId);
        filters.addFilter("objectId", "eq", objectId);
        filters.addFilter("objectType", "eq", objectType);
        mountObjectRepository.delete(filters);
        
        if(removeDiskFileFlag){
            FileInfo fileInfo = fileInfoRepository.read(fileId);
            fileInfoRepository.delete(fileInfo);
            try {
                Files.deleteIfExists(Paths.get(fileInfo.getDiskPath()));
            } catch (IOException e) {
                logger.error("删除失败!文件:"+fileInfo.getDiskPath(),e);
            }
        }
    
    }
    
    private FileInfo initFileInfo(MultipartFile file, File diskFile, String md5) {
        String name = file.getOriginalFilename();
        Long size = diskFile.length();
        String sizeDesc = FileUtil.capacityFormat(size);
        String ext = name.substring(name.lastIndexOf(".")+1);
        ext = ext.toLowerCase();
        String mimeType = MimeUtil.extToMime(ext);
        
        FileInfo fileInfo = new FileInfo();
        fileInfo.setName(name);
        fileInfo.setDiskName(diskFile.getName());
        fileInfo.setExt(ext);
        fileInfo.setSize(size);
        fileInfo.setSizeDesc(sizeDesc);
        fileInfo.setMimeType(mimeType);
        fileInfo.setDocType(this.extToDocType(ext));
        fileInfo.setMd5(md5);
        fileInfo.setDiskDir(diskFile.getParent());
        fileInfo.setDiskPath(diskFile.getPath());
        return fileInfo;
    }
    
    //文档类型 文档 视频 压缩 音频 图片
    private DocType extToDocType(String ext) {
        Map<String, AttachmentConfigPropBean.Allow> allow = attachmentConfigPropBean.getAllow();
        for (String dt : allow.keySet()) {
            AttachmentConfigPropBean.Allow properties = allow.get(dt);
            if (properties.getExtensions().contains(ext)) {
                return DocType.valueOf(dt.toUpperCase());
            }
        }
        return null;
    }
    
    private List<File> fileInfoToIOFile(List<FileInfo> fileIfs) {
        if (Objects.isNull(fileIfs)) return null;
        return fileIfs.stream().map(fileInfo -> fileInfoToIOFile(fileInfo))
                      .filter(file -> file.exists()).collect(Collectors.toList());
    }
    
    
    private File fileInfoToIOFile(FileInfo fileInfo) {
        if (Objects.isNull(fileInfo)) return null;
        return new File(fileInfo.getDiskPath());
    }
    
    public static void main(String[] args) {
    }
}
